package xin.marcher.wind.process.service.impl;

import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.ConfigType;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import xin.marcher.framework.common.exception.BusinessException;
import xin.marcher.framework.common.util.log.LoggerFormat;
import xin.marcher.wind.process.constants.ProcessConstants;
import xin.marcher.wind.process.converter.ProcessConverter;
import xin.marcher.wind.process.dao.*;
import xin.marcher.wind.process.domain.dto.BizConfigListDTO;
import xin.marcher.wind.process.domain.dto.ProcessConfigDTO;
import xin.marcher.wind.process.domain.dto.ProcessNodeLinkedDTO;
import xin.marcher.wind.process.domain.entity.*;
import xin.marcher.wind.process.domain.request.SaveProcessConfigRequest;
import xin.marcher.wind.process.enums.ProcessEnableEnum;
import xin.marcher.wind.process.enums.ProcessErrorCodeEnum;
import xin.marcher.wind.process.service.ProcessConfigService;

import javax.annotation.Resource;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

import static xin.marcher.wind.process.constants.ProcessConstants.*;

@Slf4j
@Service
public class ProcessConfigServiceImpl implements ProcessConfigService {

    @Resource
    private ProcessConfigDAO processConfigDAO;

    @Resource
    private ProcessNodeDAO processNodeDAO;

    @Resource
    private ProcessNodeLinkedDAO processNodeLinkedDAO;

    @Resource
    private ProcessBizRelationDAO processBizRelationDAO;

    @Resource
    private ProcessConverter processConverter;

    @Resource
    private ConfigService configService;

    @Resource
    private BizConfigDAO bizConfigDAO;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void save(SaveProcessConfigRequest request) {

        // 1、根据名称查询流程配置
        ProcessConfigDO processConfig = processConfigDAO.getOne(request.getName());
        if (null == processConfig) {
            processConfig = new ProcessConfigDO();
        }

        // 2、设置属性
        processConfig.setName(request.getName());
        processConfig.setXmlName(request.getXmlName());
        processConfig.setType(request.getType());
        processConfig.setRemark(request.getRemark());
        processConfig.setEnable(ProcessEnableEnum.ENABLE.getCode());

        // 3、流程构建
        List<ProcessNodeLinkedDO> processNodeLinked = buildProcessNodeLinkedDO(request.getName(), request.getProcessNodeLinked());

        // 4、业务关联关系
        List<ProcessBizRelationDO> processBizRelation = buildProcessBizRelationDO(request.getName(), request.getBizConfigRelations());

        // 5、清除之前的流程构建和业务关联关系
        processNodeLinkedDAO.deleteByProcessConfigName(request.getName());
        processBizRelationDAO.deleteByProcessConfigName(request.getName());

        // 6、重新插入流程构建和业务关联关系
        processNodeLinkedDAO.saveBatch(processNodeLinked);
        processBizRelationDAO.saveBatch(processBizRelation);

        // 7、插入流程
        processConfigDAO.saveOrUpdate(processConfig);
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delete(String name) {
        // 1、根据名称查询流程配置
        ProcessConfigDO processConfig = processConfigDAO.getOne(name);
        if (null == processConfig) {
            return;
        }

        // 2、清除关联的流程构建和业务关联关系
        processNodeLinkedDAO.deleteByProcessConfigName(name);
        processBizRelationDAO.deleteByProcessConfigName(name);

        // 3、移除自身
        processConfigDAO.deleteByName(name);
    }

    @Override
    public ProcessConfigDTO detail(String name) {

        // 1、根据名称查询流程配置
        ProcessConfigDO processConfig = processConfigDAO.getOne(name);
        if (null == processConfig) {
            return null;
        }

        // 2、构造返回参数
        return buildProcessConfigDTO(processConfig);
    }

    @Override
    public List<ProcessConfigDTO> list(List<Integer> enables) {

        List<ProcessConfigDO> processConfigs = processConfigDAO.listBy(enables);
        if (CollectionUtils.isEmpty(processConfigs)) {
            return null;
        }

        List<ProcessConfigDTO> result = new ArrayList<>(processConfigs.size());

        processConfigs.forEach(processConfigDO -> {
            result.add(buildProcessConfigDTO(processConfigDO));
        });

        return result;
    }

    @Override
    public void enable(String name, Integer enable) {
        // 1、根据名称查询流程配置
        ProcessConfigDO processConfig = processConfigDAO.getOne(name);
        if (null == processConfig) {
            return;
        }
        // 2、启用/禁用
        processConfigDAO.enable(name, enable);
    }

    @Override
    public void release() {
        // 1、查询所有启用的流程配置
        List<ProcessConfigDTO> processConfigs = list(Arrays.asList(ProcessEnableEnum.ENABLE.getCode()));

        // 2、生成流程配置xml Document
        Document document = buildProcessDocument(processConfigs);

        // 3、读取xml文件内容
        String processXml = readProcessXml(document);

        // 4、发布流程配置
        if (StringUtils.isNotBlank(processXml)) {
            publishConfig(processXml);
        }
    }

    private String readProcessXml(Document document) {
        String processXml = null;
        try {
            // 1、设置生成xml的格式,设置编码格式
            OutputFormat format = OutputFormat.createPrettyPrint();
            format.setEncoding("UTF-8");

            // 2、生成xml文件
            File file = new File(ProcessConstants.ORDER_PROCESS_XML_DATA_ID);
            XMLWriter writer = new XMLWriter(new FileOutputStream(file), format);
            writer.setEscapeText(false);
            writer.write(document);
            writer.close();
            log.info("生成{}成功", ProcessConstants.ORDER_PROCESS_XML_DATA_ID);

            // 3、读取xml文件内容
            StringBuilder xml = new StringBuilder();
            BufferedReader reader = null;
            reader = new BufferedReader(new InputStreamReader(new FileInputStream(file.getAbsoluteFile()), "utf-8"));
            String tempString = null;
            // 一次读入一行，直到读入null为文件结束
            while ((tempString = reader.readLine()) != null) {
                xml.append(tempString).append("\n");
            }
            reader.close();

            // 4、删除零时文件
            file.delete();

            // 5、获取xml内容
            processXml = xml.toString();
        } catch (Exception e) {
            log.error(LoggerFormat.build()
                    .remark("生成marcher-order-process.xml异常")
                    .data("err", e.getMessage())
                    .finish(), e);
        }
        return processXml;
    }

    private Document buildProcessDocument(List<ProcessConfigDTO> processConfigs) {
        Document document = DocumentHelper.createDocument();
        Element processContext = document.addElement(PROCESS_CONTEXT_ELEMENT, "http://www.w3school.com.cn");
        processContext.addNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
        processContext.addAttribute("xsi:schemaLocation", "http://www.w3school.com.cn process-engine.xsd");
        for (ProcessConfigDTO processConfig : processConfigs) {
            Element process = processContext.addElement(PROCESS_ELEMENT);
            process.addAttribute(NAME_ATTR, processConfig.getXmlName());

            // 增加nodes
            Element nodes = process.addElement(NODES_ELEMENT);
            List<ProcessNodeLinkedDTO> nodeLinkeds = processConfig.getProcessNodeLinked();
            int n = nodeLinkeds.size();
            for (int i = 0; i < n; i++) {
                ProcessNodeLinkedDTO nodeLinked = nodeLinkeds.get(i);
                Element node = nodes.addElement(NODE_ELEMENT);
                node.addAttribute(NAME_ATTR, nodeLinked.getProcessNodeBeanName());
                node.addAttribute(CLAZZ_ATTR, nodeLinked.getProcessNodeBeanClazzName());
                node.addAttribute(INVOKE_METHOD_ATTR, nodeLinked.getInvokeMethod().toLowerCase(Locale.ROOT));
                if (i + 1 < n) {
                    ProcessNodeLinkedDTO nextNodeLinked = nodeLinkeds.get(i + 1);
                    node.addAttribute(NEXT_ATTR, nextNodeLinked.getProcessNodeBeanName());
                }
                if (i == 0) {
                    node.addAttribute(BEGIN_ATTR, Boolean.TRUE.toString());
                }
            }

            // 增加bizRelations
            Element bizRelations = process.addElement(BIZ_RELATIONS_ELEMENT);
            for(BizConfigListDTO configListDTO : processConfig.getBizConfigRelations()) {
                Element bizConfig = bizRelations.addElement(BIZ_CONFIG_ELEMENT);
                bizConfig.addAttribute(NAME_ATTR, configListDTO.getName());
                bizConfig.addAttribute(BIZ_ID_ATTR, String.valueOf(configListDTO.getBusinessIdentifier()));
                bizConfig.addAttribute(ORDER_TYPE_ATTR, String.valueOf(configListDTO.getOrderType()));
            }
        }
        return document;
    }

    /**
     * 发布配置
     */
    private void publishConfig(String processXml) {
        try {
            log.info(LoggerFormat.build()
                    .remark("发布流程配置到nacos上去")
                    .data("processXml", processXml)
                    .data("dataId", ProcessConstants.ORDER_PROCESS_XML_DATA_ID)
                    .data("group", ProcessConstants.ORDER_PROCESS_DEFAULT_GROUP)
                    .finish());
            configService.publishConfig(ProcessConstants.ORDER_PROCESS_XML_DATA_ID,
                    ProcessConstants.ORDER_PROCESS_DEFAULT_GROUP, processXml, ConfigType.XML.getType());
        } catch (Exception e) {
            log.error(LoggerFormat.build()
                    .remark("发布流程配置异常")
                    .data("err", e.getMessage())
                    .finish(), e);
        }
    }


    private ProcessConfigDTO buildProcessConfigDTO(ProcessConfigDO processConfig) {

        // 1、查询关联的流程构建和业务关联关系
        List<ProcessNodeLinkedDO> processNodeLinked = processNodeLinkedDAO.listByProcessConfigName(processConfig.getName());
        List<ProcessBizRelationDO> processBizRelation = processBizRelationDAO.listByProcessConfigName(processConfig.getName());

        // 2、构造返参
        ProcessConfigDTO result = processConverter.convertToProcessConfigDTO(processConfig);
        result.setEnable(processConfig.getEnable());
        result.setProcessNodeLinked(buildProcessNodeLinkedDTO(processNodeLinked));
        result.setBizConfigRelations(buildBizConfigListDTO(processBizRelation));

        return result;
    }

    private List<ProcessNodeLinkedDO> buildProcessNodeLinkedDO(String processConfigName, List<ProcessNodeLinkedDTO> processNodeLinked) {
        List<ProcessNodeLinkedDO> list = new ArrayList<>();
        processNodeLinked.forEach(node -> {

            ProcessNodeDO nodeDO = processNodeDAO.getById(node.getProcessNodeId());
            if(null == nodeDO) {
                throw new BusinessException(ProcessErrorCodeEnum.PROCESS_NODE_NOT_FOUND);
            }
            if(!node.getProcessNodeName().equals(nodeDO.getName())) {
                throw new BusinessException(ProcessErrorCodeEnum.PROCESS_NODE_NAME_NOT_MATCH);
            }

            ProcessNodeLinkedDO processNodeLinkedDO = new ProcessNodeLinkedDO();
            processNodeLinkedDO.setProcessNodeName(node.getProcessNodeName());
            processNodeLinkedDO.setProcessConfigName(processConfigName);
            processNodeLinkedDO.setInvokeMethod(processNodeLinkedDO.getInvokeMethod());
            list.add(processNodeLinkedDO);
        });

        return list;
    }

    private List<ProcessBizRelationDO> buildProcessBizRelationDO(String processConfigName, List<BizConfigListDTO> bizConfigList) {
        List<ProcessBizRelationDO> list = new ArrayList<>();
        bizConfigList.forEach(biz -> {
            ProcessBizRelationDO relationDO = new ProcessBizRelationDO();
            relationDO.setBizConfigId(biz.getId());
            relationDO.setProcessConfigName(processConfigName);
            list.add(relationDO);
        });

        return list;
    }

    private List<BizConfigListDTO> buildBizConfigListDTO(List<ProcessBizRelationDO> bizRelationDOs) {
        List<BizConfigListDTO> list = new ArrayList<>();
        bizRelationDOs.forEach(relationDO -> {
            BizConfigListDTO bizConfigDTO = new BizConfigListDTO();
            BizConfigDO bizConfigDO = bizConfigDAO.getById(relationDO.getBizConfigId());
            if(null != bizConfigDO) {
                bizConfigDTO.setId(bizConfigDO.getId());
                bizConfigDTO.setName(bizConfigDO.getName());
                bizConfigDTO.setBusinessIdentifier(bizConfigDO.getBusinessIdentifier());
                bizConfigDTO.setOrderType(bizConfigDO.getOrderType());
            }

            list.add(bizConfigDTO);
        });

        return list;
    }

    private List<ProcessNodeLinkedDTO> buildProcessNodeLinkedDTO(List<ProcessNodeLinkedDO> processNodeLinkedDOs) {
        List<ProcessNodeLinkedDTO> list = new ArrayList<>();
        processNodeLinkedDOs.forEach(linkedDO -> {
            ProcessNodeLinkedDTO dto = new ProcessNodeLinkedDTO();
            dto.setProcessNodeName(linkedDO.getProcessNodeName());
            dto.setInvokeMethod(linkedDO.getInvokeMethod());
            ProcessNodeDO processNode = processNodeDAO.getOne(linkedDO.getProcessNodeName());
            dto.setProcessNodeBeanName(processNode.getBeanName());
            dto.setProcessNodeBeanClazzName(processNode.getBeanClazzName());
            list.add(dto);
        });

        return list;
    }
}
